package me.itsoo.artemis.framework.repository.executor.handler;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.itsoo.artemis.framework.core.collect.ImmutableMap;
import me.itsoo.artemis.framework.core.converter.TypeConvertor;
import me.itsoo.artemis.framework.core.converter.datetime.ChronoTypeConvertor;
import me.itsoo.artemis.framework.core.converter.number.NumberTypeConvertor;
import me.itsoo.artemis.framework.core.tuple.Pair;
import me.itsoo.artemis.framework.core.util.AnnotationUtils;
import me.itsoo.artemis.framework.core.util.Assert;
import me.itsoo.artemis.framework.core.util.CollectionUtils;
import me.itsoo.artemis.framework.core.util.ReflectUtils;
import me.itsoo.artemis.framework.core.util.StringUtils;
import me.itsoo.artemis.framework.repository.exception.UncategorizedSqlException;
import me.itsoo.artemis.framework.repository.lang.Field;
import me.itsoo.artemis.framework.repository.lang.Table;
import me.itsoo.artemis.framework.repository.mapper.MapperDefinition;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * ResultTypeHandler
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/16
 */
@Slf4j
public class ResultTypeHandler {

    private static final Map<Class<?>, ? extends TypeConvertor> TCR_POLICIES = ImmutableMap.<Class<?>, TypeConvertor>builder()
            .putAll(TypeConvertor.TYPE_CONVERTORS)
            .putAll(NumberTypeConvertor.TYPE_CONVERTORS)
            .putAll(ChronoTypeConvertor.TYPE_CONVERTORS)
            .build();

    /**
     * 自动封装结果
     *
     * @param md         MapperDefinition
     * @param resultSet  ResultSet
     * @param resultType Class
     * @param <T>        T
     * @return T
     */
    @SuppressWarnings("unchecked")
    public <T> List<T> handle(MapperDefinition md, ResultSet resultSet, Class<T> resultType) {
        final List<Map<String, Object>> mapColl = getMapCollection(resultSet);

        if (Map.class.isAssignableFrom(resultType)) {
            return (List<T>) mapColl;
        }

        if (!ReflectUtils.isConvertibleClass(resultType)) {
            return CollectionUtils.transform(mapColl, t -> {
                final Object value = CollectionUtils.findFirst(t.values());
                return (T) convertIfNecessary(resultType, value, null);
            });
        }

        final MapperDefinition rmd = computeIfAbsent(md, resultType);
        return CollectionUtils.transform(mapColl, t -> toInstance(rmd, t, resultType));
    }

    /**
     * 获取 MapperDefinition
     *
     * @param md         MapperDefinition
     * @param resultType Class
     * @return MapperDefinition
     */
    private MapperDefinition computeIfAbsent(MapperDefinition md, Class<?> resultType) {
        if (Objects.isNull(md) || !md.isAssignableFrom(resultType)) {
            final Table ann = AnnotationUtils.findAnnotation(resultType, Table.class);
            if (Objects.nonNull(ann)) {
                md = MapperDefinition.create(resultType);
            }
        }

        if (Objects.isNull(md)) {
            throw new UncategorizedSqlException("Compute 'md' failed, type of " + resultType.getName());
        }

        return md;
    }

    /**
     * 获取 Map 结果集
     *
     * @param resultSet ResultSet
     * @return Collection
     */
    @SneakyThrows(SQLException.class)
    private List<Map<String, Object>> getMapCollection(ResultSet resultSet) {
        try (ResultSet rs = resultSet) {
            final List<Map<String, Object>> result = new ArrayList<>(rs.getFetchSize());
            final ResultSetMetaData rmd = rs.getMetaData();

            while (rs.next()) {
                final Map<String, Object> rowData = new LinkedHashMap<>(32);
                for (int i = 1, len = rmd.getColumnCount(); i <= len; i++) {
                    final String fieldName = StringUtils.defaultIfBlank(rmd.getColumnLabel(i), rmd.getColumnName(i));
                    rowData.put(fieldName, rs.getObject(fieldName));
                }
                result.add(rowData);
            }

            return result;
        }
    }

    /**
     * 转换为实例对象
     *
     * @param md         MapperDefinition
     * @param valueMap   Map
     * @param resultType Class
     * @param <T>        T
     * @return T
     */
    private <T> T toInstance(MapperDefinition md, Map<String, Object> valueMap, Class<T> resultType) {
        final T result = ReflectUtils.newInstance(resultType);

        if (Objects.nonNull(result)) {
            for (Pair<String, java.lang.reflect.Field> fn : md.getFieldNames()) {
                final Object srcVal = valueMap.get(fn.getKey());
                final Object tarVal = convertIfNecessary(fn.getValue(), srcVal);
                ReflectUtils.setFieldValue(fn.getValue(), result, tarVal);
            }
        }

        return result;
    }

    /**
     * 字段值转换
     *
     * @param field 目标字段
     * @param value 原始值
     * @return 目标值
     */
    private Object convertIfNecessary(java.lang.reflect.Field field, Object value) {
        final Class<?> clazz = ReflectUtils.getGenericClass(field.getGenericType());
        final TypeConvertor typeConvertor = TCR_POLICIES.get(clazz);

        if (typeConvertor instanceof ChronoTypeConvertor) {
            final Field ann = AnnotationUtils.findAnnotation(field, Field.class);
            Assert.notNull(ann, field.getName() + ": cannot found field with @Field");
            return convertIfNecessary(clazz, value, ann.format());
        }

        return convertIfNecessary(clazz, value, null);
    }

    /**
     * 数据类型转换
     *
     * @param clazz  Class
     * @param value  Object
     * @param format String
     * @return Object
     */
    private Object convertIfNecessary(Class<?> clazz, Object value, String format) {
        final TypeConvertor typeConvertor = TCR_POLICIES.get(clazz);

        try {
            if (typeConvertor instanceof ChronoTypeConvertor) {
                return ((ChronoTypeConvertor) typeConvertor).convertIfNecessary(value, format);
            }
            return typeConvertor.convertIfNecessary(value);
        } catch (Exception e) {
            log.error("Field type converted error: [{}], {}", clazz, value);
            return null;
        }
    }
}
